/* 
 * Copyright 2011 kOSMik.
 * This file is part of kOSMik.
 * 
 * kOSMik is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * kOSMik is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with kOSMik.  If not, see <http://www.gnu.org/licenses/>.
 *  
 * kOSMik:
 * Open Street Map - Editor.
 * http://code.google.com/p/swp-dv-ws2010-osm-2/
 */

package de.fuberlin.kosmik.data.osmt;

import android.content.ContentValues;
import android.database.Cursor;

/**
 * This class encapsulate an row of the Value table and defines read and write
 * operations.
 * 
 * @author Johannes
 * 
 */
public class OsmtValue implements OsmtTable {

	/** Name of table Value. */
	static final String TABLE = "Value";
	/** 1. Column name. */
	static final String COLUMN_OSM_NAME = "Value.osm_name";
	/** 2. Column name. */
	static final String COLUMN_USE = "Value.use_stat";
	private final OsmtDatabase osmtDatabase;
	private final OsmtValueRecord record = new OsmtValueRecord();

	/**
	 * Constructor.
	 * 
	 * @param osmtData specifies the database
	 * @param osmName The name of the value. If the value already exists in the
	 *            database table, the corresponding row will be load. Otherwise
	 *            a new value will be created and inserted into the database.
	 * @throws OsmtObjectException if this object cannot be created. That can
	 *             happen, if the value neither could read from the database nor
	 *             could written into.
	 */
	public OsmtValue(OsmtData osmtData, String osmName) throws OsmtObjectException {
		super();
		this.osmtDatabase = ((OsmtDataImpl) osmtData).getDatabase();
		if (!set(osmName))
			throw new OsmtObjectException(String.format(
					"can neither load nor create value %s.", osmName));
	}

	/**
	 * Constructor.
	 * 
	 * @param osmtData specifies the database
	 * @param osmValueRecord The record containing the values of an table row.
	 *            This constructor considered only the name attribute. If an
	 *            value with this name already exists in the database table, the
	 *            corresponding row will be load. Otherwise a new value will be
	 *            created and inserted into the database.
	 * @throws OsmtObjectException if this object cannot be created. That can
	 *             happen, if the value neither could read from the database nor
	 *             could written into.
	 */
	public OsmtValue(OsmtData osmtData, OsmtValueRecord osmValueRecord)
			throws OsmtObjectException {
		this(osmtData, osmValueRecord.osmName);
	}

	/**
	 * Sets the values direct without consistency checks.
	 * 
	 * @param osmtValueRecord The record with all relevant data.
	 */
	void setTrusted(OsmtValueRecord osmtValueRecord) {
		record.osmName = osmtValueRecord.osmName;
		record.use = osmtValueRecord.use;
	}

	/**
	 * Sets the values direct without consistency checks.
	 * 
	 * @param osmName the osm name for the value
	 * @param use the number how often this value used at all
	 */
	void setTrusted(String osmName, int use) {
		record.osmName = osmName;
		record.use = use;
	}

	/**
	 * Set to a new value.
	 * 
	 * @param osmName The name of the value the object should be set to. If the
	 *            value already exists in the database table, the corresponding
	 *            row will be load. Otherwise a new value will be created and
	 *            inserted into the database.
	 * @return true, if the value could be set.
	 */
	public boolean set(String osmName) {
		this.record.osmName = osmName;
		return loadValueByName(this, osmName) || tryInsertAsNewValue(this);
	}

	/**
	 * Set to a new value. Ignores the use attribute inside the OsmtValueRecord.
	 * 
	 * @param osmtValueRecord the structure that contain the data. Furthermore,
	 *            it is like {@link OsmtValue#set(String)}
	 * @return true, if the value could be set.
	 */
	public boolean set(OsmtValueRecord osmtValueRecord) {
		this.record.osmName = osmtValueRecord.osmName;
		return loadValueByName(this, osmtValueRecord.osmName) || tryInsertAsNewValue(this);
	}

	/**
	 * @return the record containing the pure data
	 */
	public OsmtValueRecord get() {
		return record;
	}

	/**
	 * Load data from db into the attributes of the given object.
	 * 
	 * @return true, if the specified value was known by db, otherwise false
	 */
	private static boolean loadValueByName(final OsmtValue osmtValue, final String osmName) {
		final String lookupByName = String.format("%s='%s'", COLUMN_OSM_NAME, osmName);
		final OsmtQuery query = new OsmtQuery(TABLE, null, lookupByName);
		final boolean valueFound = osmtValue.osmtDatabase.query(query,
				new OnObtainCursorListener() {

					@Override
					public boolean onObtainCursor(Cursor cursor) {
						if (cursor.moveToFirst()) {
							final int osmNameIndex = cursor.getColumnIndex(COLUMN_OSM_NAME);
							final int useIndex = cursor.getColumnIndex(COLUMN_USE);
							osmtValue.setTrusted(cursor.getString(osmNameIndex),
									cursor.getInt(useIndex));
							return true;
						}
						return false;
					}
				});
		return valueFound;
	}

	/**
	 * Try to insert a value into the database specified in the given
	 * OsmValue-object.
	 * 
	 * @param osmtValue the value that should be written
	 * @return true, if a new row inserted into the database, otherwise false
	 */
	private static boolean tryInsertAsNewValue(final OsmtValue osmtValue) {
		final ContentValues contentValues = new ContentValues();
		contentValues.put(COLUMN_OSM_NAME, osmtValue.record.osmName);
		return osmtValue.osmtDatabase.insertRecord(osmtValue, contentValues);
	}

	/**
	 * Use this method to signalize the statistic, that this osm element was
	 * used by a user one more time.
	 * 
	 * @param osmtValue the value the use counter should be increment.
	 */
	public static void useOneMoreTime(final OsmtValue osmtValue) {
		final ContentValues contentValues = new ContentValues();
		final String lookupByName = String.format("%s='%s'", COLUMN_OSM_NAME,
				osmtValue.record.osmName);
		contentValues.put(COLUMN_USE, Integer.valueOf(++osmtValue.record.use));
		osmtValue.osmtDatabase.update(osmtValue, contentValues, lookupByName, null);
	}

	/**
	 * Tries to delete the value under awareness of constraints.
	 * 
	 * @param osmtValue the value that should be deleted
	 * @return true, if the value removed successfully, otherwise false
	 */
	public static boolean remove(final OsmtValue osmtValue) {
		// TODO: check constraints , cascade
		final String lookupByName = String.format("%s='%s'", COLUMN_OSM_NAME,
				osmtValue.record.osmName);
		// TODO: check at first, that this value is not referenced by anyone
		// else
		return osmtValue.osmtDatabase.delete(osmtValue, lookupByName, null);
	}

	@Override
	public String getTableName() {
		return TABLE;
	}

}
